神秘菜鸟

好吧其实一点也不神秘,但菜是真的

泰拉瑞亚修改教程 — 中

大纲
  1. 1. 第二章 — 利用脚本进行复杂的修改
    1. 1.1. 一击必杀
    2. 1.2. 无敌
    3. 1.3. 极速
    4. 1.4. 无限召唤
    5. 1.5. 备用,后续再更。

这是泰拉瑞亚修改教程的第二章,上接第一章,如果你还没有看过相关教程,可以从头看起。

第二章 — 利用脚本进行复杂的修改

一击必杀

一击必杀的功能其实直接修改武器伤害为9999也可以实现,但是特殊情况下就会失效,比如面对地牢守卫时,你的每次攻击永远只能对它造成1滴血的伤害,这就导致了无论你修改多高的伤害打到它的身上也只会扣1滴血。

地牢守卫

既然高伤害不能实现秒杀,那就换一个思路,我们知道当你攻击一个怪物时,是两个触发事件,第一你的武器摸到了怪物,第二怪物扣除相应的血量,第一个不用研究,第二个怪物扣除血量我们可以改为当攻击到怪物后,不管怪物的血量是多少直接把怪物的血量变为0,变为0那当然就死了。下面是实现方法:

首先找到怪物的血值地址,随便找个怪物查看它的当前血量,这里史莱姆为例:

怪物血值

可看到史莱姆的血量为120,那在CE里搜索4字节的120数值看看:

CE界面

看到结果还有很多,那再过滤一下,回到游戏攻击一下史莱姆:

游戏界面

攻击一下扣了2滴血,返回CE继续搜索118:

CE界面

可以看到此时只有一个结果了,右键这个地址,查看是什么改写了这个地址

CE界面

回到游戏,再攻击一下史莱姆,然后返回CE,应该可以看到列表里多了一个地址,点击Show disassembler打开内存浏览界面:

CE界面

然后在这个地址上右键,复制其模块地址:

CE界面

然后点击Tools -> Auto Assemble

CE界面

再点击Template -> Full Injection,然后框内粘贴刚才复制的模块地址:

CE界面

然后分析下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{ Game   : Terraria.exe
Version:
Date : 2022-02-17
Author : admin

This script does blah blah blah
}

define(address,Terraria.NPC::StrikeNPC+742)
define(bytes,29 86 00 01 00 00)

[ENABLE]

assert(address,bytes)
alloc(newmem,$1000)

label(code)
label(return)

newmem:

code:
sub [esi+00000100],eax
jmp return

address:
jmp newmem
nop
return:

[DISABLE]

address:
db bytes
// sub [esi+00000100],eax

dealloc(newmem)

定位到sub [esi+00000100],eax,这段的意思是从[esi+00000100]这个指针地址里减去eax,因为我们是攻击怪物的时候触发的这个地址,所以很可能[esi+00000100]就是怪物的血值地址,而eax即扣除的血量数值。

既然怀疑,就尝试修改一下,因为sub [esi+00000100],eax的意思是从怪物血值里减去受到的伤害值,那我们注释掉原来的sub [esi+00000100],eax,改为mov [esi+00000100],0,这段意思是,把0这个值移到怪物血值里,这样当我们攻击怪物的时候触发的就是把0移到他的血量里面,相当于血量直接变为0,也就达到了秒杀效果:

CE界面

然后保存脚本:

CE界面

接着激活脚本:

CE界面

最后进入游戏看看有无效果:

一击必杀效果

可以看到满血史莱姆一镐子就没了,当然不止史莱姆,任何怪物都是一击必杀,因为懒的原因我就不去找其他怪物试刀了,你们可以自行测试。

到这里一击必杀的效果就做出来了,而且不是修改高伤害那种伪·一击必杀,其实游戏修改很简单,难点在于思路,只要找对了思路,问题就会迎刃而解,当然也需要一些汇编基础才能看懂代码,但是最重要的还是思路。

无敌

既然我们拥有了最强的矛,那当然也得拥有最强的盾不是,和一击必杀类似,也有伪·无敌,找到人物血量基址,然后锁定血量即可达到受伤秒回复的效果,但是这样有个弊端就是如果遇到伤害很高的怪物,或者怪物群殴你的时候,就很有可能血量还没回复就被敌人秒杀了,所以不算真正的无敌,那么如何实现真正的无敌呢。

同样的,当我们被攻击的时候也是两个触发事件,第一怪物或者怪物的技能碰到了我们的角色,第二我们的角色扣除相应的血量。看到这里是不是很熟悉,你说那我是不是可以根据第二个触发事件来修改,当怪物触碰到我们的时候,把那个受到的伤害值改为0?这样每次受到攻击都是无效的0点伤害,当然可以,你甚至还能把那个扣血的事件改为加血事件,这样当怪物攻击你的时候,你不仅不掉血,而且还加血,这样每个怪物在你眼里就成了无数个奶妈。

那下面试着实现一下这两个思路,首先找到角色的血量地址,进入游戏,查看当前的血量,可看到我当前的血量是79:

角色当前血量

返回CE搜索4字节的79:

CE界面

还有很多结果,返回游戏让怪物攻击一下,再查看当前血量变成了61:

当前血量

返回CE,搜索61,看到还剩两个结果:

CE界面

通过修改血量数值判断出第二个为角色血量地址:

CE界面

右键该地址,查看是什么改写了这个地址

CE界面

返回游戏,让怪物攻击一下,再返回CE,此时看到多了一个地址:

其实到这里,和一击必杀的步骤都是一样的,根据那个步骤操作就行,这里咱们直接快进到代码部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{ Game   : Terraria.exe
Version:
Date : 2022-02-18
Author : admin

This script does blah blah blah
}

define(address,Terraria.Player::Hurt+1177)
define(bytes,29 82 E4 03 00 00)

[ENABLE]

assert(address,bytes)
alloc(newmem,$1000)

label(code)
label(return)

newmem:

code:
sub [edx+000003E4],eax
jmp return

address:
jmp newmem
nop
return:

[DISABLE]

address:
db bytes
// sub [edx+000003E4],eax

dealloc(newmem)

定位到sub [edx+000003E4],eax,如果你认真学习了一击必杀那个功能的实现,那你现在肯定知道[edx+000003E4]就是角色血量的地址,而eax即为受到的伤害值,sub是一个汇编指令,表示,之前的mov代表数据传送,下面马上要用到的add表示,其实很好理解,根据字面意思大概都能猜出来。那么这段的意思就是从当前血量里扣除受到的伤害值,我现在把eax改为0,那是不是就代表着,每次受到攻击都是0伤害值?

注释掉sub [edx+000003E4],eax,增加一条语句为sub [edx+000003E4],0

接着保存并激活脚本,进入游戏看看效果:

无敌效果

可以看到虽然还是有显示受到的伤害数字,但是并不会掉血,无敌效果①达成。

下面试下另一个效果,其实也很简单,只需要改动一点点代码:

可以看到原代码为sub [edx+000003E4],eax,代表从血值里扣掉受到的伤害值,之前我们改为sub [edx+000003E4],0,就表示从血值里扣掉0点伤害值,相当于不受伤,现在我们改为add [edx+000003E4],1,表示受到伤害时,把1加到血值里面,这样每次受到攻击都会加1滴血。

保存并激活脚本,进游戏看看效果:

无敌效果2

很好,无敌效果②达成。

但是会发现,有两个不完美的地方:①会显示受到伤害的数字,明明都已经无敌了,还显示个毛的伤害数字啊;②受到攻击时虽然不掉血,但是会被怪物撞得跳来跳去的,明明都已经无敌了,你这还疼得跳来跳去的算什么事啊?

那么问题来了,有第三种无敌方法吗?

俗话说得好,只要思想不滑坡,办法总比困难多,开头的时候说过,当被攻击的时候触发的是两个事件,一被怪物触碰到,二扣除所受伤害值的血量,我们根据第二个事件研究出了两个无敌思路,现在研究第一个事件,因为我们是先被怪物触碰到然后才会触发扣血的事件,那么如果移除这个碰撞事件,是不是就不会受到伤害了?

前面我们找到了扣血函数的地址:Terraria.Player::Hurt+1177,现在我们在这个地址下断点,看看能否找到关于碰撞的函数:

下断点后返回游戏,随便找个怪挨打一下,挨打后会发现游戏被断下了,那证明这个地址有戏,分析一下,因为是先挨打再扣血,而我们是直接在扣血地址下的断点,那这个call大概率只是处理一些扣血以及死亡的函数,我们点击Step Out跳出这个call:

跳出这个call后来到这个地址:

这里我们发现上面这个地址call了Terraria.Player::Hurt,根据翻译猜测这个Terraria.Player::Hurt应该是处理角色受到伤害的一个总函数,那我们直接nop掉这个地址是不是就不会受到碰撞伤害了呢:

nop会提示该地址长度为6字节,但是nop只有一个字节,点yes会自动填充剩下的5字节。然后点Run再回游戏看看效果:

可以看到已经移除了碰撞检测,那我们还原刚才nop的代码,通过编写脚本来达到目的,和之前一样,复制其模块地址,然后编写脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{ Game   : Terraria.exe
Version:
Date : 2022-02-19
Author : admin

This script doe blah blah blah
}

define(address,Terraria.Player::Update_NPCCollision+7A1)
define(bytes,E8 8A FE D9 FF)

[ENABLE]

assert(address,bytes)
alloc(newmem,$1000)

label(code)
label(return)

newmem:

code:
call Terraria.Player::Hurt
jmp return

address:
jmp newmem
return:

[DISABLE]

address:
db bytes
// call Terraria.Player::Hurt

dealloc(newmem)

刚才我们是直接nop掉达到了目的,那么脚本如何编写呢,其实很简单,注释掉call Terraria.Player::Hurt就行了,注释掉就不会执行这个call:

和之前一样,保存脚本,然后激活脚本进游戏测试效果:

到这里,无敌功能就算是完美了,涉及到一些新东西如下断点,nop,call等等都是汇编知识,感兴趣的可以找相关书籍看看,也不用深究,这玩意学多了脑壳疼,单纯的写写修改器有点基础就够了,写修改器最重要的是思路。

极速

天下武功,唯快不破,只要我跑得够快,敌人就摸不到我。这个功能就来讲一下如何修改移动速度,别的不说,用来跑图是真的好使。

如何找到移动速度这个地址呢,毕竟你不知道初始移动速度是多少,跑起来又是多少。但是如果你学过上一章的话,那你肯定记得利用角色基址来获取到其他地址这个操作,这里就不再赘述,没看过的去上一章看看。

这里通过搜索speed关键词看能不能搜索出移动速度的地址来:

很明显这个moveSpeed就是我们要找的移动速度,我们按A键把这个地址添加到CE列表里:

可以看到它的值为单精度浮点数的1,那表明我们移动的时候速度为1,尝试修改一下为5,但是会发现返回游戏自动就改回1了,也就是说我们无法直接修改。

查看是什么改写了这个地址

返回游戏会发现CE里跳出了一个地址,看到它的指令为fstp dword ptr [esi+00000458],意思是将栈顶的值储存到[esi+00000458]这个指针地址里,然后弹出栈。既然下面有出栈,上面肯定就有一个入栈,我们浏览相关内存区域看看:

果然发现上面有一个fld1,它表示将浮点数1储存到栈顶:

分析一下,fld1将1压入栈顶,然后fstp dword ptr [esi+00000458]又将1储存到[esi+00000458]里,那显而易见的,[esi+00000458]这个指针地址里储存的就是移动速度的值。

那么老规矩,在这个地址上写脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{ Game   : Terraria.exe
Version:
Date : 2022-02-20
Author : admin

This script does blah blah blah
}

define(address,Terraria.Player::ResetEffects+FE)
define(bytes,D9 9E 58 04 00 00)

[ENABLE]

assert(address,bytes)
alloc(newmem,$1000)

label(code)
label(return)

newmem:

code:
fstp dword ptr [esi+00000458]
jmp return

address:
jmp newmem
nop
return:

[DISABLE]

address:
db bytes
// fstp dword ptr [esi+00000458]

dealloc(newmem)

既然fstp dword ptr [esi+00000458]是将1存到移动速度的地址里,那导致我们的移动速度只有1,现在我们不要这段指令,注释掉,然后我们通过mov指令把我们想要的速度值放到速度的指针地址里,比如5:

请注意,移动速度是一个单精度的浮点数,需要在5的前面加个(float)强制转为浮点数,如果直接写整数的话肯定会出问题,因为浮点数在内存的存储方式与整数不一样。

保存脚本后激活脚本进游戏看看效果:

效果显著,如果你觉得5还是慢了,可以再改高一点,但是不宜过高,因为实测体验不好。

无限召唤

现在我们最强的矛与盾都有了,已经站在了世界之巅,这时候就需要一群小弟来衬托自己的身份,毕竟打个史莱姆都要自己动手显得很没有逼格。但是泰拉瑞亚原版最多只能召唤11个小弟,开玩笑,世界最强怎么只能拥有11个小弟,这个功能就讲讲怎么突破召唤限制。

老规矩,通过角色基址找到最大召唤数量的地址:

这里通过搜索max找到了maxMinions,这就是我们要找的最大召唤数量的地址,下面的numMinions是目前召唤出来的小弟数量,我们按A键把maxMinions添加到CE地址列表:

直接修改的话发现会被系统自动改回来,那右键这个地址查看是什么改写了它,返回游戏触发一下会发现CE里多了一个地址,我们选中这个地址,并浏览相关内存区域:

看到后面的mov [esi+00000298],00000001,很明显[esi+00000298]就是最大召唤数量,那么意思就是把1移到最大召唤数量的地址里:

那么我们通过脚本把这个1改为我们想要的最大召唤数量就行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{ Game   : Terraria.exe
Version:
Date : 2022-02-22
Author : admin

This script does blah blah blah
}

define(address,Terraria.Player::ResetEffects+30A)
define(bytes,C7 86 98 02 00 00 01 00 00 00)

[ENABLE]

assert(address,bytes)
alloc(newmem,$1000)

label(code)
label(return)

newmem:

code:
mov [esi+00000298],00000001
jmp return

address:
jmp newmem
nop 5
return:

[DISABLE]

address:
db bytes
// mov [esi+00000298],00000001

dealloc(newmem)

这应该算是最容易编写的一个脚本了吧,只需要改动一下数字就可以了,它是一个十六进制的4字节数字,比如我改为000000FF对应的十进制数量就是255个:

如果你觉得255个少了,可以适量增加,但是过多的召唤物会增加你电脑的负担,255个完全够用了,保存脚本并激活它,进入游戏看看效果:

可以看见虽然才255个,但是肉眼可见的卡顿,电脑配置不好的话就很影响体验。

这里我修改了星尘龙法杖的弹幕数量,所以挥一下就能召唤很多个,如果你是放在物品栏第一个格子修改的话,弹幕数量在inventory - Array[0] - useTime,对应的偏移为D0 - 8 - AC,值越小数量越多,改为0就能一下召唤很多个了。

备用,后续再更。